package org.smallframework.spring;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import sf.database.jdbc.rowmapper.BeanRowMapper;
import sf.database.jdbc.rowmapper.RowMapperHelp;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * support jpa annotations.
 * @param <T>
 */
public class SormBeanPropertyRowMapper<T> implements RowMapper<T> {

    /**
     * Logger available to subclasses.
     */
    protected final Log logger = LogFactory.getLog(getClass());

    /**
     * The class we are mapping to.
     */
    @Nullable
    private Class<T> mappedClass;


    /**
     * Create a new {@code SormBeanPropertyRowMapper} for bean-style configuration.
     * @see #setMappedClass
     */
    public SormBeanPropertyRowMapper() {
    }

    /**
     * Create a new {@code SormBeanPropertyRowMapper}, accepting unpopulated
     * properties in the target bean.
     * <p>Consider using the {@link #newInstance} factory method instead,
     * which allows for specifying the mapped type once only.
     * @param mappedClass the class that each row should be mapped to
     */
    public SormBeanPropertyRowMapper(Class<T> mappedClass) {
        initialize(mappedClass);
    }

    /**
     * Set the class that each row should be mapped to.
     */
    public void setMappedClass(Class<T> mappedClass) {
        if (this.mappedClass == null) {
            initialize(mappedClass);
        } else {
            if (this.mappedClass != mappedClass) {
                throw new InvalidDataAccessApiUsageException("The mapped class can not be reassigned to map to " +
                        mappedClass + " since it is already providing mapping for " + this.mappedClass);
            }
        }
    }

    /**
     * Get the class that we are mapping to.
     */
    @Nullable
    public final Class<T> getMappedClass() {
        return this.mappedClass;
    }


    /**
     * Initialize the mapping meta-data for the given class.
     * @param mappedClass the mapped class
     */
    protected void initialize(Class<T> mappedClass) {
        this.mappedClass = mappedClass;
    }

    /**
     * Extract the values for all columns in the current row.
     * <p>Utilizes public setters and result set meta-data.
     * @see java.sql.ResultSetMetaData
     */
    @Override
    public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
        Assert.state(this.mappedClass != null, "Mapped class was not specified");
        BeanRowMapper<T> beanRowMapper = RowMapperHelp.getBeanRowMapper(this.mappedClass, null);
        return beanRowMapper.handle(rs, rs.getMetaData(), rowNumber);
    }

    /**
     * Static factory method to create a new {@code SormBeanPropertyRowMapper}
     * (with the mapped class specified only once).
     * @param mappedClass the class that each row should be mapped to
     * @param <T>
     * @return
     */
    public static <T> SormBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) {
        return new SormBeanPropertyRowMapper<>(mappedClass);
    }
}

